Vue前端路由
前置准备
参考资料 云中桥「前端进阶」彻底弄懂前端路由
路由是一个比较广义和抽象的概念,路由的本质就是对应关系;开发中,路由分为 后端路由、前端路由
后端路由
根据不同的用户 URL 请求,返回不同的内容。就是 URL 请求地址与服务器资源之间的对应关系
SPA 技术
SPA(Single page Application)单页面应用程序:就是一个 WEB 项目只有一个 HTML 页面,一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用 JS 动态的变换(Ajax 局部更新) HTML 的内容,从而来模拟多个视图间跳转。例如一些固定的页头和页尾是不变的,只有内容变更了。所以只需动态的替换中间的内容就好了
但由于 SPA 中用户的交互是通过 JS 改变 HTML 内容来实现的,页面本身的 url 并没有变化,这导致了两个问题:
1、SPA 无法记住用户的操作记录,无论是刷新、前进还是后退,都无法展示用户真实的期望内容。
2、SPA 中虽然由于业务的不同会有多种页面展示形式,但只有一个 url,对 SEO 不友好,不方便搜索引擎进行收录。
所以就引入了前端路由的技术
前端路由
简单的说,就是在保证只有一个 HTML 页面,且与用户交互时不刷新和跳转页面的同时,为 SPA 中的每个视图展示形式匹配一个特殊的 url。在刷新、前进、后退和SEO时均通过这个特殊的 url 来实现。
为实现这一目标,需要做到以下二点:
1、改变 url 且不让浏览器向服务器发送请求。
2、可以监听到 url 的变化
实现原理:基于 URL 地址的 hash (就是锚点),hash的变化会导致浏览器访问记录的变化、但是hash的变化不会触发新的 URL 请求
就是 URL 后面跟着的 #
一般用于页面内跳转
http://localhost/data/#abc
实现简易的前端路由
基于 URL 中的 hash 实现(点击菜单的时候改变URL的hash,根据hash的变化控制组件的切换)
使用 Window 对象的 onhashchange
事件,根据获取到的最新的 hash 值,切换要显示的组件的名称
window.onhashchange = function(){
// 通过 location.hash 获取到最新的 hash 值
}
配置环境
在 Vue 后面加载 vue-router,它会自动安装的:
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
如果是 NPM
npm install vue-router
如果在一个模块化工程中使用它,必须要通过 Vue.use()
明确地安装路由功能:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
Vue Router包含的功能有:
- 支持 HTML5 历史模式或 hash 模式
- 支持嵌套路由
- 支持路由参数
- 支持编程式路由
- 支持命名路由
前端路由基本使用
基本使用步骤
1、引入库文件
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
2、添加路由链接
<!-- router-link 是 vue 中提供的标签,默认会被渲染成 a 标签 -->
<!-- to 属性默认会被渲染成 href 属性 -->
<!-- to 属性的值默认会被渲染成 # 开头的 hash 地址 -->
<router-link to="/user">User</router-link>
<router-link to="/register">Register</router-link>
3、添加路由填充位
<!-- 路由填充位(也叫路由占位符) -->
<!-- 将来通过路由规则匹配到的组件,将会被渲染到 router-view 所在位置 -->
<router-view></router-view>
4、定义路由组件
const User = {
template: '<h1>User 组件</h1>'
}
const Register = {
template: '<h1>Register 组件</h1>'
}
5、配置路由规则并创建路由实例
// 创建路由实例
const router = new VueRouter({
// 路由规则数组
routes: [
// 每个路由规则都是一个配置对象,其中至少包含 path 和 component 两个属性
// path 表示当前路由规则匹配的 hash 地址
// component 表示当前路由规则对应要展示的组件
{ path: '/user', component: User },
{ path: '/register', component: Register }
]
})
6、把路由挂载到 Vue 根实例中
new Vue({
el: '#app',
// 挂载到 Vue 实例对象上
router: router // 属性和属性值一样可以简写成:router
})
路由重定向
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C,从而展示特定的组件页面
通过路由规则的 redirect 属性,指定一个新的路由地址
// 创建路由实例
const router = new VueRouter({
// 路由规则数组
routes: [
// path 表示需要被重定向的原地址,redirect 表示要被重定向到的新地址
{ path: '/', redirect: '/user' },
{ path: '/user', component: User },
{ path: '/register', component: Register }
]
})
嵌套路由
点击父级路由链接显示模板内容,模板内容中又有子路由链接
只需在模板里加上就好了
const Register = {
template: `
<div>
<h1>Register 组件</h1>
<hr/>
<router-link to="/register/table1">Table1</router-link>
<router-link to="/register/table2">Table2</router-link>
<!-- 子路由填充位 -->
<router-view></router-view>
</div>
`
}
const Table1 = {
template: '<h3>Table1 子组件</h3>'
}
const Table2 = {
template: '<h3>Table2 子组件</h3>'
}
// 创建路由实例
const router = new VueRouter({
// 路由规则数组
routes: [
// path 表示需要被重定向的原地址,redirect 表示要被重定向到的新地址
{ path: '/', redirect: '/user' },
{ path: '/user', component: User },
{
path: '/register', component: Register, children: [
// 设置子路由规则
{ path: '/register/table1', component: Table1 },
{ path: '/register/table2', component: Table2 },
]
}
]
})
编程式导航
导航有两种方式:
1、声明式导航:通过点击链接实现导航的方式,叫做声明式导航
例如:普通网页中的 <a></a>
链接 或 vue 中的 router-link
2、编程式导航:通过调用 JavaScript 形式的 API 实现导航的方式,叫做编程式导航
例如:普通网页中的 location.href
常用的编程式导航 API
this.$router.push('hash地址')
// 传递一个数字,1表示前进,-1表示后退
this.$router.go(number)
例如点击按钮跳转到注册页面
const User = {
template: '<div><button @click="goRegister">跳转到注册页面</button></div>',
methods: {
// 注意这里不能用箭头函数
goRegister:function(){
this.$router.push('/register')
}
},
}
router.push() 方法的参数规则
除了上面直接写路由字符串的方式,还能传递参数进去
// 字符串(路径名称)
router.push('/home')
// 对象
router.push({path: '/home'})
// 命名的路由(传递参数)
router.push({name: 'home', params: {userId: 123}})
// 带查询参数,变成 /register?uname=lisa
router.push({path: '/home', query: {uname: 'lisa'}})
动态路由
动态路由匹配
当某些路由一部分是完全一样的,只有小部分参数是动态变化的时可以用
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-view></router-view>
const User = {
// 路由组件中通过 $route.params 获取路由参数
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
路由组件传递参数
前面的 $route.params
和对应路由形成高耦合。所以可以使用 props 将组件和路由解耦
注:这个 props 是 Vue 组件化开发的内容了,不了解的回顾去
因为路由本质就是点击了之后在某块地方填充组件嘛,所以以前怎么传递组件参数的,这里一样可以使用
当 props 的值是 Boolean 类型时
const router = new VueRouter({
routes: [
// 如果 props 被设置为 true,route.params 将会被设置为组件属性
// 开启 props 传参
{ path: '/user/:id', component: User , props: true}
]
})
const User = {
// 使用 props 接收路由参数
props: ['id'],
// 使用路由参数
template: '<div>User 的 ID 为:{{ id }}</div>'
}
当 props 的值是对象类型时
它会按原样设置为组件属性,但是这时的 id 是访问不到的
const router = new VueRouter({
routes: [
// 如果 props 是一个对象,它会按原样设置为组件属性,但是这时的 id 是访问不到的(就算是下面的 props 有接收这个参数)
{ path: '/user/:id', component: User , props: {uname: 'list', age: 12}}
]
})
const User = {
// 使用 props 接收路由参数
props: ['uname', 'age'],
// 使用路由参数
template: "<div>用户信息为:{{ uname + '----' + age}}</div>"
}
props 的值为函数类型时
这种方式可以既传递 id 又传递 “对象”
const router = new VueRouter({
routes: [
// 如果 props 是一个对象,则这个函数接收 router 对象为形参
// 注意这个 route =>({ uname: '张三', age: 20 ,id: route.params.id }) 表示 return { uname: '张三', age: 20 ,id: route.params.id } 对象
{ path: '/user/:id', component: User , props: route =>({
uname: '张三', age: 20 ,id: route.params.id
})}
]
})
const User = {
// 使用 props 接收路由参数
props: ['uname', 'age' ,'id'],
// 使用路由参数
template: "<div>用户信息为:{{ uname + '----' + age + '----' + id}}</div>"
}
命名路由的匹配规则
为了更加方便的表示路由的路径,可以给路由规则起一个别名,即为“命名路由”
const router = new VueRouter({
routes: [
{ path: '/user/:id', name: 'user' ,component: User }
]
})
然后在 router-link
里绑定这个命名
<!-- 注意 to 前面要加冒号 -->
<router-link :to="{name: 'user', params: {id: 123}}">User</router-link>
路由导航守卫
控制登陆权限,当没有登陆时禁止访问特定页面,需要重新导航到登陆页面
// 为路由对象添加 beforeEach 导航守卫
router.beforeEach((to, from, next) =>{
// 如果用户访问的登录页,直接放行
if(to.path === '/login') return next()
// 否则需要从 sessionStorage 中取得保存的 token 值
const tokenStr = window.sessionStorage.getItem('token')
// 没有 token,强制跳转到登录页
if(!tokenStr) return next('/login')
next()
})
基于 token 的方式退出只需销毁本地的 token 即可。这样后续的请求就不会携带 token,必须重新登陆生成一个新的 token 之后才能访问页面
// 清空 token
window.sessionStorage.clear()
// 跳转到登陆页
this.$router.push('/login')
路由案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>基于vue-router的案例</title>
<style type="text/css">
html,
body,
#app {
margin: 0;
padding: 0px;
height: 100%;
}
.header {
height: 50px;
background-color: #545c64;
line-height: 50px;
text-align: center;
font-size: 24px;
color: #fff;
}
.footer {
height: 40px;
line-height: 40px;
background-color: #888;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
}
.main {
display: flex;
position: absolute;
top: 50px;
bottom: 40px;
width: 100%;
}
.content {
flex: 1;
text-align: center;
height: 100%;
}
.left {
flex: 0 0 20%;
background-color: #545c64;
}
.left a {
color: white;
text-decoration: none;
}
.right {
margin: 5px;
}
.btns {
width: 100%;
height: 35px;
line-height: 35px;
background-color: #f5f5f5;
text-align: left;
padding-left: 10px;
box-sizing: border-box;
}
button {
height: 30px;
background-color: #ecf5ff;
border: 1px solid lightskyblue;
font-size: 12px;
padding: 0 20px;
}
.main-content {
margin-top: 10px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
ul li {
height: 45px;
line-height: 45px;
background-color: #a0a0a0;
color: #fff;
cursor: pointer;
border-bottom: 1px solid #fff;
}
table {
width: 100%;
border-collapse: collapse;
}
td,
th {
border: 1px solid #eee;
line-height: 35px;
font-size: 12px;
}
th {
background-color: #ddd;
}
</style>
</head>
<body>
<div>
<div id="app">
<router-view></router-view>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script>
const APP = {
template: `
<div>
<!-- 头部区域 -->
<header class="header">传智后台管理系统</header>
<!-- 中间主体区域 -->
<div class="main">
<!-- 左侧菜单栏 -->
<div class="content left">
<ul>
<li><router-link to="/users">用户管理</router-link></li>
<li><router-link to="/rights">权限管理</router-link></li>
<li><router-link to="/goods">商品管理</router-link></li>
<li><router-link to="/orders">订单管理</router-link></li>
<li><router-link to="/setting">系统设置</router-link></li>
</ul>
</div>
<!-- 右侧内容区域 -->
<div class="content right">
<div class="main-content">
<router-view/>
</div>
</div>
</div>
<!-- 尾部区域 -->
<footer class="footer">版权信息</footer>
</div>
`
}
const User = {
data() {
return {
userlist: [
{ id: 1, name: '张三', age: 10 },
{ id: 2, name: '李四', age: 15 },
{ id: 3, name: '王五', age: 20 },
{ id: 4, name: '赵六', age: 25 }
]
}
},
methods: {
goDetail(id) {
console.log(id);
// 动态参数
this.$router.push(`/userInfo/${id}`)
}
},
template: `
<div>
<h3>用户管理区域</h3>
<table>
<thead>
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in userlist" :key="index">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td><a href="Javascript:;" @click="goDetail(item.id)">详情</a></td>
</tr>
</tbody>
</table>
</div>
`
}
const UserInfo = {
props:['id'],
methods: {
goBack(){
this.$router.go(-1)
}
},
template: `
<div>
<h5>用户详情页 -- {{id}}</h5>
<button @click="goBack()">后退</button>
</div>
`
}
const Rights = {
template: `
<div>
<h3>权限管理区域</h3>
</div>
`
}
const Goods = {
template: `
<div>
<h3>商品管理区域</h3>
</div>
`
}
const Orders = {
template: `
<div>
<h3>订单管理区域</h3>
</div>
`
}
const Setting = {
template: `
<div>
<h3>系统设置区域</h3>
</div>
`
}
const router = new VueRouter({
routes: [{
path: '/',
component: APP,
redirect: '/users', // 路由重定向
children: [
{ path: '/users', component: User },
{ path: '/userInfo/:id', component: UserInfo, props: true },
{ path: '/rights', component: Rights },
{ path: '/goods', component: Goods },
{ path: '/orders', component: Orders },
{ path: '/setting', component: Setting },
]
}]
})
const vm = new Vue({
el: '#app',
router: router
})
</script>
</div>
</body>
</html>